using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using Alchemi.Core.Owner;
using Renderer.DataStructures;
using Renderer.Utils;

namespace Renderer
{
    [Serializable]
    class RendererThread : GThread
    {
        private Bitmap resultPicure;                
        
        /// <summary>
        /// This variable contains all triangles of the scene
        /// </summary>
        private Scene scene;

        /// <summary>
        /// Size of the screen (plane on which scene will be projected)
        /// </summary>
        private Rectangle screenSize;
        
        /// <summary>
        /// Row-index of the part of the screen to render
        /// </summary>
        private int row;

        /// <summary>
        /// Column-index of the part of the screen to render
        /// </summary>
        private int col;
                
        /// <summary>
        /// Size of the part of the screen to render in this RenderThread
        /// </summary>
        private Rectangle viewportSize;

        /// <summary>
        /// Ratio between width and height of the screen
        /// </summary>
        private readonly double aspectRatio;

        private readonly double distanceToScreen;
        
        public int Row
        {
            get { return row; }
        }
        
        public int Col
        {
            get { return col; }
        }
        
        public Rectangle ViewportSize
        {
            get { return viewportSize; }
        }
        
        public Rectangle ScreenSize
        {
            get { return screenSize; }
        }
        
        public Bitmap ResultPicure
        {
            get { return resultPicure; }
        }

        public Scene Scene
        {
            get { return scene; }
        }

        public RendererThread(Scene scene, int row, int col, Rectangle viewportSize, Rectangle screenSize)
        {
            this.scene = scene;
            this.row = row;
            this.col = col;
            this.viewportSize = viewportSize;
            this.screenSize = screenSize;
            aspectRatio = (double)screenSize.Height / screenSize.Width;
            distanceToScreen = screenSize.Width / (2 * Math.Tan(scene.FOV/2));
        }
        
        public override void Start()
        {
            Logger.Log("In Start. row: " + row + ", col: " + col + ", width: " + viewportSize.Width + ", height: " + viewportSize.Height + ", screen width: " + screenSize.Width + ", screen height: " + screenSize.Height + ", distanceToScreen: " + distanceToScreen);
            try
            {
                resultPicure = new Bitmap(viewportSize.Width, viewportSize.Height);
                
                // calculate vector which is perpendicular to the left size of the camera
                // and point to the left
                Vector3D toLeftOfCamera = (scene.ToUpOfCamera ^ scene.CameraDirection).Normalize();

                Logger.Log("ToUpOfCamera: " + scene.ToUpOfCamera + ", CameraDirection: " + scene.CameraDirection + ", toLeftOfCamera: " + toLeftOfCamera);
                
                // calculate coordinates of the top left cornder of the current part 
                // of the screen to render (in the camera-oriented coordinate system)
                double cameraOrientedX = screenSize.Width / 2 - col * viewportSize.Width;
                double cameraOrientedZ = screenSize.Width / 2 * aspectRatio - row * viewportSize.Height;
                double cameraOrientedY = distanceToScreen;

                Logger.Log("CameraOriented Top Left X : " + cameraOrientedX + ", CameraOriented Top Left Y : " + cameraOrientedY + ", CameraOriented Top Left Z : " + cameraOrientedZ);

                Color color;
                
                for (int i = 0; i < viewportSize.Height; i++)
                {
                    for (int j = 0; j < viewportSize.Width; j++)
                    {
                        // camera-oriented coordinates of the point on the screen 
                        // whose color we are goint to calculate
                        Vector3D pointOfInterest = new Vector3D(cameraOrientedX - j, cameraOrientedY, cameraOrientedZ - i);
                        
                        // transform camera-oriented coordinate of the top-left corner to the scene-oriented
                        pointOfInterest = scene.CameraPosition - pointOfInterest.X * toLeftOfCamera + pointOfInterest.Z * scene.ToUpOfCamera + pointOfInterest.Y * scene.CameraDirection;
                        
                        
                        color = CalculateColor(new Ray3D(scene.CameraPosition, pointOfInterest - scene.CameraPosition));
                        resultPicure.SetPixel(j, i, color);
                    }
                }
            }
            catch(Exception e)
            {
                ExceptionHandler.Handle("in RendererThread#Start", e);
            }
        }        
        
        /// <summary>
        /// This method calculates the color of the point
        /// which is the intersection point of the given 'ray' and one 
        /// of the obejcts (currently only triangles are available) of the scene
        /// </summary>
        /// <param name="ray"></param>
        /// <returns></returns>
        private Color CalculateColor(Ray3D ray)
        {
            //Logger.Log("Return color for origin : " + ray.Origin + " , dir : " + ray.Direction);
            
            // default color is black
            Color result = Color.Black; // Color.FromArgb(1, 0, 0, 0);

            Vector3D point;
            Triangle triangle;

            List<Triangle> triangles = new List<Triangle>();
            triangles.AddRange(scene.Triangles);
            triangles.AddRange(scene.LightTriangles);
            
            double distance = MathHelper.GetNearestIntersection(ray, triangles, out point, out triangle);            
            
            if (distance >= double.MaxValue - MathHelper.EPS )
            {
                //Logger.Log(" No intersection, color: " + result);
                return result;
            }
            //Logger.Log(" Distance : " + distance + ", maxDouble: " + double.MaxValue + ", point " + point + ", triangle: " + triangle + ", color: " + triangle.Color);
            
            // TODO: implement shading (basing on the location of the point towrads all the lights of the scene)
            
            return triangle.Color;
        }
    }
}
